/**
 * MacAddress stores an Ethernet MAC address as a String and provides methods
 * for comparison between two MAC addresses.
 * 
 * @author Jonathan Jung, Timothy Hong, Ronen Adam
 * 
 */
// TODO: Exceptions?
public class MacAddress implements Comparable<MacAddress> {
    private String address;
    private byte[] addressAsBytes;
    /*
     * A byte's range is -128 to 127, so an integer value needs to be offset by
     * 128 so that it occupies the correct "step" of the byte. Ex: (69 of type
     * int) minus 128 equals (-59 of type byte). 69 - 128 = -59
     */
    private final int BYTE_OFFSET = 128;
    private final int MAX_SINGLE_DIGIT = -112;
    private final int FF_AS_BYTE = 127;

    /**
     * Creates a MacAddress using the specified byte array. The address is
     * stored as a String without delimiters.
     * 
     * @param addressAsBytes
     *            - The array of bytes containing the MacAddress values.
     */
    public MacAddress(byte[] addressAsBytes) {

        this.addressAsBytes = addressAsBytes;

        StringBuilder result = new StringBuilder();

        for (int i = 0; i < addressAsBytes.length; i++) {
            // Pad a leading '0' to single-digit values of the pair.
            if (addressAsBytes[i] < MAX_SINGLE_DIGIT) {
                result.append("0");
            }

            /*
             * Adjust the value of the pair by 128 so the range is 0 to 255. and
             * represent the pair's value as a hex string.
             */
            result.append(Integer
                    .toHexString((addressAsBytes[i] + BYTE_OFFSET)));
        }

        // Store the address as a String.
        result.trimToSize();
        address = result.substring(0);
    }

    public MacAddress(String IP, short port) {
        byte[] part1;
        byte[] part2;
        part1 = ipToByteArray(IP);
        part2 = shortToByteArray(port);
        addressAsBytes = new byte[6];
        for (int x = 0; x < part1.length; x++) {
            addressAsBytes[x] = part1[x];
        }
        for (int y = 0; y < part2.length; y++) {
            addressAsBytes[y] = part2[y];
        }
    }

    /**
     * Creates a MacAddress using the reserved address for broadcast.
     * ff:ff:ff:ff:ff:ff The address is stored as a String without delimiters.
     */
    public MacAddress() {
        // Special address for broadcast. ff:ff:ff:ff:ff:ff
        // Representing ff as a byte.

        address = "ffffffffffff";

        byte[] temp = { FF_AS_BYTE, FF_AS_BYTE, FF_AS_BYTE, FF_AS_BYTE,
                FF_AS_BYTE, FF_AS_BYTE };
        addressAsBytes = temp;
    }

    /**
     * Returns a String representation of a MacAddress with the six octets of an
     * Ethernet MAC address separated by a ':'.
     * 
     * @return Returns a String representation of a MacAddress.
     */
    public String toString() {
        StringBuilder result = new StringBuilder();

        // Separate the six pairs with a ':'.
        int index = 0;

        // The first 5 pairs.
        for (int i = 0; i < 5; i++) {
            result.append(address.substring(index, index + 2));
            result.append(":");
            index += 2;
        }

        // The sixth pair.
        result.append(address.substring(index, index + 2));

        result.trimToSize();

        return result.substring(0);
    }

    /**
     * Compares two MacAddresses.
     * 
     * @param otherAddress
     *            - The address being compared.
     * @return Returns a negative integer, zero, or a positive integer if this
     *         address is less than, equal to, or greater than the address being
     *         compared.
     */
    @Override
    public int compareTo(MacAddress otherAddress) {

        if (address.compareTo(otherAddress.address) < 0) {
            return -1;
        } else if (address.compareTo(otherAddress.address) > 0) {
            return 1;
        }

        return 0;
    }

    /**
     * Indicates whether one MacAddress is equal to another.
     * 
     * @param object
     *            - The object with which to compare.
     * @return Returns <B>true</B> if the MacAddresses match. Returns
     *         <B>false</B> otherwise.
     */
    @Override
    public boolean equals(Object object) {

        MacAddress otherAddress = (MacAddress) object;

        return address.equals(otherAddress.address);
    }

    public byte[] toByteArray() {

        return addressAsBytes;
    }

    private byte[] shortToByteArray(short value) {
        byte upper = (byte) (value >> 8);
        byte lower = (byte) (value & 0xFF);

        byte[] array = { upper, lower };

        return array;
    }

    private byte[] ipToByteArray(String ip) {
        // Strip leading '/'.
        if (ip.substring(0, 1).equals("/")) {
            ip = ip.substring(1);
        }

        // Isolate each octet from the IP string.
        String[] addressSlices = ip.split("\\.");

        // Assemble an integer ip value from the octets.
        // Shift each octet to its appropriate position in a 32-bit value.
        int octet1 = Integer.valueOf(addressSlices[0]);
        int octet2 = Integer.valueOf(addressSlices[1]);
        int octet3 = Integer.valueOf(addressSlices[2]);
        int octet4 = Integer.valueOf(addressSlices[3]);

        byte[] array = new byte[4];

        array[0] = Integer.valueOf(octet1).byteValue();
        array[1] = Integer.valueOf(octet2).byteValue();
        array[2] = Integer.valueOf(octet3).byteValue();
        array[3] = Integer.valueOf(octet4).byteValue();

        return array;
    }
}
